package queue;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.Executor;

import com.google.protobuf.CodedInputStream;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.Descriptors.FieldDescriptor;

public class Server {
	
	private final ServerSocket serverSocket;
	
	private boolean running;
	
	//@Property
	private int port;
	public Server setPort(int port)
		{ this.port = port; return this; }
	
	//@Property
	private Message prototype;
	public Server setPrototype(Message prototype)
		{ this.prototype = prototype; return this; }
	
	//@Property
	private Executor executor;
	public Server setExecutor(Executor executor)
		{ this.executor = executor; return this; }
	
	public Server() {
		try {
			serverSocket = new ServerSocket();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	public void stop() {
		running = false;
		try {
			serverSocket.close();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	public void start() {
		running = true;
		executor.execute(new Runnable() {
			public void run() {
				try {
					serverSocket.bind(new InetSocketAddress(port));
					while(running) {
						System.out.println("Accept... ");
						final Socket socket = serverSocket.accept();
						System.out.println(socket);
						executor.execute(new Runnable() {
							public void run() {
								while(running) {
									try {
										int length = readLength(socket.getInputStream());
										System.out.println(String.format("Server: %d", length));
										byte[] buf = new byte[length];
										socket.getInputStream().read(buf);
										System.out.println(String.format("Server: %s", Arrays.toString(buf)));
										Message message = prototype.newBuilderForType().mergeFrom(buf).build();
										Map<FieldDescriptor, Object> fields = message.getAllFields();
										for(FieldDescriptor descriptor : fields.keySet()) {
											System.out.println(
												String.format(
													"%s: %s",
													descriptor.getName(),
													fields.get(descriptor)));
										}
									} catch (IOException e) {
										try {
											socket.close();
										} catch (IOException e1) {
											throw new RuntimeException(e);
										}
										throw new RuntimeException(e);
									}									
								}
							}});
					}
				} catch (IOException e) {
					throw new RuntimeException(e);
				} finally {
					try {
						serverSocket.close();
					} catch (IOException e) {
						throw new RuntimeException(e);
					}
				}
			}});
	}
	
	private int readLength(InputStream is) throws IOException {
		int n = -1;
		byte[] buf = new byte[5];
		is.read(buf, 0, 1);
		try {
			n = CodedInputStream.newInstance(buf, 0, 1).readRawVarint32();
		} catch (InvalidProtocolBufferException e0) {
			is.read(buf, 1, 1);	
			try {
				n = CodedInputStream.newInstance(buf, 0, 2).readRawVarint32();
			} catch (InvalidProtocolBufferException e1) {
				is.read(buf, 2, 1);
				try {
					n = CodedInputStream.newInstance(buf, 0, 3).readRawVarint32();
				} catch (InvalidProtocolBufferException e2) {
					is.read(buf, 3, 1);
					try {
						n = CodedInputStream.newInstance(buf, 0, 4).readRawVarint32();
					} catch (InvalidProtocolBufferException e3) {
						is.read(buf, 4, 1);
						n = CodedInputStream.newInstance(buf, 0, 5).readRawVarint32();
					}	
				}	
			}								
		}
		return n;
	}
	
}
